#include "audio_types.h"
#include "audio_mixer.h"

#include <list>

using namespace krryn::audio;

mixer::mixer(output::ptr a_Driver)
	: m_Output(a_Driver), m_MasterVolume(1.f), m_BufferDuration(10 milliseconds) {}
mixer::~mixer() {}

void mixer::update(){
	std::list<sample::ptr> l_Buffer;

	int l_MaxNumberOfSamples = 0;
	for(source_list_t::iterator i = m_Active.begin(); i != m_Active.end(); i++){
		sample::ptr l_Data = (*i)->next(m_BufferDuration);

		if(l_Data){
			// resample the sample if it's in a 
			// different sample rate than what
			// the output expects.
			l_Data = resample(l_Data);

			int l_Count = l_Data->count();

			if(l_Data->channels() == 1)
				l_Count *= 2;

			if(l_Count > l_MaxNumberOfSamples)
				l_MaxNumberOfSamples = l_Count;

			l_Buffer.push_back(l_Data);
		}
	}

	sample::ptr l_Result(new sample(m_Output->sample_rate(), 2));

	// allocate twice as much data for stereo output
	l_Result->allocate(l_MaxNumberOfSamples);

	// clear the buffer
	sample::value_type *l_Tmp = l_Result->buffer();
	for(int i = 0; i < l_Result->count(); i++) *l_Tmp++ = 0;

	for(std::list<sample::ptr>::iterator i = l_Buffer.begin(); i != l_Buffer.end(); i++){
		mix(*i, &l_Result);
	}
	
	m_Output->write(l_Result);
}

void mixer::play(input::ptr a_Sound){
	m_Active.push_back(a_Sound);
}

void mixer::set_buffer_duration(int a_MilliSeconds){
	m_BufferDuration = a_MilliSeconds;
}

void mixer::set_volume(float a_Volume){
	m_MasterVolume = a_Volume;
}

int mixer::buffer_duration(){
	return m_BufferDuration;
}

float mixer::volume(){
	return m_MasterVolume;
}

void mixer::mix(sample::ptr a_Sample, sample::ptr *a_Result){
	sample::value_type *l_Left   = a_Sample->buffer();
	sample::value_type *l_Right  = a_Sample->buffer();
	sample::value_type *l_Output = (*a_Result)->buffer();

	float l_PanValue = std::min(1.f, std::max(-1.f, a_Sample->pan()));

	sample::value_type l_PanLeft  = 1 - ((l_PanValue + 1) / 2);
	sample::value_type l_PanRight =      (l_PanValue + 1) / 2;
	
	float l_MagicValue = 5.f;

	// correct right channel for interleaved format
	l_Right += a_Sample->channels() - 1;

	for(int i = 0; i < a_Sample->count() / a_Sample->channels(); i++){
		float l_Volume = a_Sample->volume() * m_MasterVolume / l_MagicValue;

		*l_Output++ += *l_Left * l_PanLeft * l_Volume;
		*l_Output++ += *l_Right * l_PanRight * l_Volume;

		l_Left  += a_Sample->channels();
		l_Right += a_Sample->channels();
	}
}

sample::ptr mixer::resample(sample::ptr a_Input){
	int l_SampleRate = m_Output->sample_rate();

	if(a_Input->sample_rate() == l_SampleRate){
		return a_Input;
	}

	sample::ptr l_OutSample(new sample(l_SampleRate, 2));

	l_OutSample->allocate(a_Input->count() * l_SampleRate / a_Input->sample_rate());

	// copy some values from the input sample
	l_OutSample->set_volume(a_Input->volume());
	l_OutSample->set_pan(a_Input->pan());

	sample::value_type *l_Input  = a_Input->buffer();
	sample::value_type *l_Output = l_OutSample->buffer();

	for(int i = 0; i < l_OutSample->count(); i++){
		int l_Target = i * a_Input->sample_rate();
		int l_Index = l_Target / l_SampleRate;
		int l_Pos = l_Target % l_SampleRate;

		// for better quality we would actually have to
		// have a buffer that is several elements larger
		// than that it's supposed to be but I think we
		// can get away with this implementation for now.
		// if we don't do this, l_Next will point to memory
		// outside of l_Input and effectively causing a
		// buffer overflow that causes noticable distortion
		// in the output so by capping l_Next we basically
		// skip the interpolation for the last element.
		int l_Next = std::min(l_Index + l_OutSample->channels(), a_Input->count() - 1);

		float l_Percentage = (float)l_Pos / (float)l_SampleRate;

		// simple linear interpolation.
		*l_Output++ =
			  l_Input[l_Index] * (1 - l_Percentage) 
			+ l_Input[l_Next] * l_Percentage;
	}

	return l_OutSample;
}
